package com.lxm.enigma.core;

import com.lxm.enigma.annotations.*;
import com.lxm.enigma.enums.CipherType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @Author: Lys
 * @Date 2022/5/31
 * @Describe
 **/
@Slf4j
public class CipherRegister implements ApplicationContextAware {

    private final List<String> encStreamArray = new ArrayList<>();
    private final List<String> encDataArray = new ArrayList<>();
    private final List<String> decArray = new ArrayList<>();
    private final List<String> signArray = new ArrayList<>();

    private Map<String, Object> controllers;

    /**
     * 如果注解打在controller上，则无视所有method上的注解
     */
    private boolean scanMethods = true;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.controllers = applicationContext.getBeansWithAnnotation(RestController.class);
        this.readControllers();
        // 找到所有需要处理的加解密stream类型之后，赋值给config
        EnigmaConfig config = applicationContext.getBean(EnigmaConfig.class);
        config.setDecArray(this.decArray);
        config.setEncDataArray(this.encDataArray);
        config.setEncStreamArray(this.encStreamArray);
        config.setSignArray(this.signArray);
    }

    /**
     * 遍历所有带有@RestController的conpoment
     */
    private void readControllers() {
        if (ObjectUtils.isEmpty(this.controllers)) {
            log.debug("enigma cipher register , 0 controllers found");
            return;
        }
        for (Object controller : controllers.values()) {
            var clz = controller.getClass();
            var mapping = AnnotationUtils.findAnnotation(clz, RequestMapping.class);
            String uri = "";
            // 提取uri前缀
            if (Objects.nonNull(mapping)) {
                uri += Arrays.toString(mapping.value());
                uri = StringUtils.replace(uri, "[", "");
                uri = StringUtils.replace(uri, "]", "");
                if (StringUtils.isNotBlank(uri) && !StringUtils.startsWith(uri, "/")) {
                    uri = "/" + uri;
                }
            }
            var methods = clz.getMethods();
            if (ObjectUtils.isEmpty(methods)) {
                continue;
            }

            // 先从controller读注解。读不到，再从methods读。能读到，则跳过methods。
            // 简单说加解密注解在controller优先级高于methods
            // 且一个地方只能打一个加解密注解
            readAnnotationOnController(uri, clz, methods);

            if (!this.scanMethods) {
                continue;
            }

            for (Method method : methods) {
                readAnnotationOnSingleMethod(uri, method);
            }
        }
    }

    /**
     * 找打在controller上的注解。如果有，所有method的注解就无效了，以controller为准
     *
     * @param uriPrefix
     * @param methods
     */
    private void readAnnotationOnController(String uriPrefix, Class<?> clazz, Method[] methods) {
        boolean hasSign = Objects.nonNull(AnnotationUtils.findAnnotation(clazz, CipherSign.class));
        if (hasSign) {
            for (Method method : methods) {
                var signList = retrieveUris(uriPrefix, CipherType.SIGN, method);
                this.signArray.addAll(signList);
            }
            // 阻断
            scanMethods = false;
            return;
        }

        // 双向加解密data & stream
        boolean hasCipherData = Objects.nonNull(AnnotationUtils.findAnnotation(clazz, CipherData.class));
        boolean hasCipherStream = Objects.nonNull(AnnotationUtils.findAnnotation(clazz, CipherStream.class));
        if (hasCipherData || hasCipherStream) {
            for (Method method : methods) {
                var decList = retrieveUris(uriPrefix, CipherType.DEC, method);
                this.decArray.addAll(decList);
                // data类型优先
                if (hasCipherData) {
                    var encDataList = retrieveUris(uriPrefix, CipherType.ENC_DATA, method);
                    this.encDataArray.addAll(encDataList);
                } else {
                    var encStreamList = retrieveUris(uriPrefix, CipherType.ENC_STREAM, method);
                    this.encStreamArray.addAll(encStreamList);
                }
            }
            // 阻断
            scanMethods = false;
            return;
        }

        // data的加密
        boolean hasEncipherData = Objects.nonNull(AnnotationUtils.findAnnotation(clazz, EncipherData.class));
        if (hasEncipherData) {
            for (Method method : methods) {
                var encDataList = retrieveUris(uriPrefix, CipherType.ENC_DATA, method);
                this.encDataArray.addAll(encDataList);
            }
            // 阻断
            scanMethods = false;
            return;
        }

        // stream的加密
        boolean hasEncipherStream = Objects.nonNull(AnnotationUtils.findAnnotation(clazz, EncipherStream.class));
        if (hasEncipherStream) {
            for (Method method : methods) {
                var encStreamList = retrieveUris(uriPrefix, CipherType.ENC_STREAM, method);
                this.encStreamArray.addAll(encStreamList);
            }
            // 阻断
            scanMethods = false;
        }
    }

    /**
     * 遍历单个controller里的所有methods，找加解密的注解
     *
     * @param uriPrefix uri前缀，一般来说写在@RequestMapping里
     * @param method
     */
    private void readAnnotationOnSingleMethod(String uriPrefix, Method method) {
        var useSign = AnnotationUtils.findAnnotation(method, CipherSign.class);
        boolean hasSign = Objects.nonNull(useSign);
        if (hasSign) {
            var signList = retrieveUris(uriPrefix, CipherType.SIGN, method);
            this.signArray.addAll(signList);
        } else {
            // 解密类型，data和stream都是一样
            var cipherData = AnnotationUtils.findAnnotation(method, CipherData.class);
            var cipherStream = AnnotationUtils.findAnnotation(method, CipherStream.class);
            boolean hasDeciphers = ObjectUtils.anyNotNull(cipherData, cipherStream);
            if (hasDeciphers) {
                var decList = retrieveUris(uriPrefix, CipherType.DEC, method);
                this.decArray.addAll(decList);
            }

            boolean hasDecipherData = Objects.nonNull(cipherData);
            boolean hasDecipherStream = Objects.nonNull(cipherStream);

            // 加密类型区分data和stream
            var encData = AnnotationUtils.findAnnotation(method, EncipherData.class);
            boolean hasEncData = Objects.nonNull(encData);
            if (hasEncData || hasDecipherData) {
                var encDataList = retrieveUris(uriPrefix, CipherType.ENC_DATA, method);
                this.encDataArray.addAll(encDataList);
            }

            var encStream = AnnotationUtils.findAnnotation(method, EncipherStream.class);
            boolean hasEncStream = Objects.nonNull(encStream);
            if (hasEncStream || hasDecipherStream) {
                var encStreamList = retrieveUris(uriPrefix, CipherType.ENC_STREAM, method);
                this.encStreamArray.addAll(encStreamList);
            }
        }

    }

    /**
     * 提取每个method里的uri
     *
     * @param uriPrefix
     * @param cipherType
     * @param method
     * @return
     */
    private List<String> retrieveUris(String uriPrefix, CipherType cipherType, Method method) {
        List<String> uris = new ArrayList<>();

        if (cipherType.equals(CipherType.SIGN)) {
            // sign类型，只适用于post
            uris.addAll(retrieveFromPostMapping(uriPrefix, method));
        } else {
            // 其他类型，均可用
            uris.addAll(retrieveFromRequestMapping(uriPrefix, method));
            uris.addAll(retrieveFromPostMapping(uriPrefix, method));
            uris.addAll(retrieveFromGetMapping(uriPrefix, method));
            uris.addAll(retrieveFromDeleteMapping(uriPrefix, method));
            uris.addAll(retrieveFromPutMapping(uriPrefix, method));
        }
        return uris;
    }

    private List<String> retrieveFromRequestMapping(String uriPrefix, Method method) {
        List<String> uris = new ArrayList<>();
        var r = AnnotationUtils.findAnnotation(method, RequestMapping.class);//method.getAnnotation(RequestMapping.class);
        if (Objects.nonNull(r) && r.value().length > 0) {
            RequestMethod[] methodStr = r.method();
            String[] uriArray = r.value();
            for (RequestMethod rm : methodStr) {
                for (String u : uriArray) {
                    String temp = clearUri(uriPrefix, rm.name(), u);
                    uris.add(temp);
                }
            }
        }
        return uris;
    }

    private List<String> retrieveFromPostMapping(String uriPrefix, Method method) {
        List<String> uris = new ArrayList<>();
        var p = AnnotationUtils.findAnnotation(method, PostMapping.class);//method.getAnnotation(PostMapping.class);
        if (Objects.nonNull(p)) {
            String[] uriArray = p.value();
            for (String s : uriArray) {
                String temp = clearUri(uriPrefix, "POST", s);
                uris.add(temp);
            }
        }
        return uris;
    }

    private List<String> retrieveFromGetMapping(String uriPrefix, Method method) {
        List<String> uris = new ArrayList<>();
        var g = AnnotationUtils.findAnnotation(method, GetMapping.class);//method.getAnnotation(GetMapping.class);
        if (Objects.nonNull(g)) {
            String[] uriArray = g.value();
            for (String u : uriArray) {
                String temp = clearUri(uriPrefix, "GET", u);
                uris.add(temp);
            }
        }
        return uris;
    }

    private List<String> retrieveFromDeleteMapping(String uriPrefix, Method method) {
        List<String> uris = new ArrayList<>();
        var d = AnnotationUtils.findAnnotation(method, DeleteMapping.class);//method.getAnnotation(DeleteMapping.class);
        if (Objects.nonNull(d)) {
            String[] uriArray = d.value();
            for (String s : uriArray) {
                String temp = clearUri(uriPrefix, "DELETE", s);
                uris.add(temp);
            }
        }
        return uris;
    }

    private List<String> retrieveFromPutMapping(String uriPrefix, Method method) {
        List<String> uris = new ArrayList<>();
        var pu = AnnotationUtils.findAnnotation(method, PutMapping.class);//method.getAnnotation(PutMapping.class);
        if (Objects.nonNull(pu)) {
            String[] uriArray = pu.value();
            for (String s : uriArray) {
                String temp = clearUri(uriPrefix, "PUT", s);
                uris.add(temp);
            }
        }
        return uris;
    }

    /**
     * 把方法和uri拼接在一起，举例：POST:/web/v1
     *
     * @param baseUri
     * @param method
     * @param uri
     * @return
     */
    private String clearUri(String baseUri, String method, String uri) {
        if (!StringUtils.startsWith(uri, "/")) {
            uri = "/" + uri;
        }
        return method + ":" + baseUri + uri;
    }

}
